/**
 * Custom Matter Behavior Classes for Homebridge
 *
 * These custom behaviors extend the base Matter.js behaviors and override
 * command methods to allow plugins to inject custom handlers.
 *
 * Note: Only clusters with user-triggered commands need custom behaviors.
 * Read-only clusters (like sensors) don't need custom behaviors since
 * they only report state, they don't receive commands.
 */

import type { MatterCommandHandler } from './matterTypes.js'

import {
  ColorControlServer,
  DoorLockServer,
  IdentifyServer,
  LevelControlServer,
  OnOffServer,
  ThermostatServer,
  WindowCoveringServer,
} from '@matter/main/behaviors'

import { Logger } from '../logger.js'

const log = Logger.withPrefix('Matter')

/**
 * Store for custom handlers
 * Maps endpoint ID -> cluster name -> command name -> handler
 */
const handlerStore = new Map<string, Map<string, Map<string, MatterCommandHandler>>>()

/**
 * Store for accessory references
 * This allows handlers to update cached clusters after state changes
 */
let accessoriesMap: Map<string, any> | null = null

/**
 * Set the accessories map reference for cache syncing
 */
export function setAccessoriesMap(map: Map<string, any>) {
  accessoriesMap = map
}

/**
 * Sync endpoint state back to cached clusters
 * This ensures state changes from handlers persist across restarts
 */
function syncEndpointStateToCache(endpointId: string, clusterName: string, attributes: Record<string, any>) {
  if (!accessoriesMap) {
    return
  }

  const accessory = accessoriesMap.get(endpointId)
  if (!accessory) {
    return
  }

  // Update the cached clusters with the new state
  if (!accessory.clusters[clusterName]) {
    accessory.clusters[clusterName] = {}
  }
  accessory.clusters[clusterName] = {
    ...accessory.clusters[clusterName],
    ...attributes,
  }

  log.debug(`Synced ${clusterName} state to cache for ${endpointId}:`, attributes)
}

/**
 * Register a handler for a specific endpoint/cluster/command
 *
 * @param endpointId - Unique endpoint identifier (typically the accessory UUID)
 * @param clusterName - Name of the Matter cluster (e.g., 'onOff', 'levelControl')
 * @param commandName - Name of the command method (e.g., 'on', 'off', 'moveToLevel')
 * @param handler - Callback function to execute when the command is received
 *
 * @example
 * ```typescript
 * registerHandler('my-light-uuid', 'onOff', 'on', async () => {
 *   console.log('Light turned on!')
 * })
 * ```
 */
export function registerHandler(
  endpointId: string,
  clusterName: string,
  commandName: string,
  handler: MatterCommandHandler,
) {
  if (!handlerStore.has(endpointId)) {
    handlerStore.set(endpointId, new Map())
  }
  const endpointHandlers = handlerStore.get(endpointId)!

  if (!endpointHandlers.has(clusterName)) {
    endpointHandlers.set(clusterName, new Map())
  }
  const clusterHandlers = endpointHandlers.get(clusterName)!

  clusterHandlers.set(commandName, handler)
  log.info(`Registered handler for ${endpointId}.${clusterName}.${commandName}`)
}

/**
 * Get a handler for a specific endpoint/cluster/command
 */
function getHandler(endpointId: string, clusterName: string, commandName: string) {
  return handlerStore.get(endpointId)?.get(clusterName)?.get(commandName)
}

/**
 * Custom OnOff Server that calls plugin handlers
 */
export class HomebridgeOnOffServer extends OnOffServer {
  override on(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'onOff', 'on')

    log.info(`OnOff.on called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    } else {
      log.warn(`  ⚠ No handler registered for ${endpointId}.onOff.on`)
    }

    const result = super.on()

    // Sync the new state to cache for persistence
    syncEndpointStateToCache(endpointId, 'onOff', { onOff: true })

    return result
  }

  override off(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'onOff', 'off')

    log.info(`OnOff.off called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    } else {
      log.warn(`  ⚠ No handler registered for ${endpointId}.onOff.off`)
    }

    const result = super.off()

    // Sync the new state to cache for persistence
    syncEndpointStateToCache(endpointId, 'onOff', { onOff: false })

    return result
  }

  override toggle(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'onOff', 'toggle')

    log.info(`OnOff.toggle called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.toggle()

    // Read the new state from the endpoint and sync to cache
    const newState = (this.state as any).onOff
    syncEndpointStateToCache(endpointId, 'onOff', { onOff: newState })

    return result
  }
}

/**
 * Custom LevelControl Server that calls plugin handlers
 */
export class HomebridgeLevelControlServer extends LevelControlServer {
  override moveToLevel(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'levelControl', 'moveToLevel')

    log.info(`LevelControl.moveToLevel called for endpoint ${endpointId} with level ${request.level}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    } else {
      log.warn(`  ⚠ No handler registered for ${endpointId}.levelControl.moveToLevel`)
    }

    const result = super.moveToLevel(request)

    // Sync the new level to cache for persistence
    syncEndpointStateToCache(endpointId, 'levelControl', { currentLevel: request.level })

    return result
  }

  override move(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'levelControl', 'move')

    log.info(`LevelControl.move called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    return super.move(request)
  }

  override step(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'levelControl', 'step')

    log.info(`LevelControl.step called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    return super.step(request)
  }

  override stop(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'levelControl', 'stop')

    log.info(`LevelControl.stop called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    return super.stop(request)
  }
}

/**
 * Custom WindowCovering Server that calls plugin handlers
 */
export class HomebridgeWindowCoveringServer extends WindowCoveringServer {
  override upOrOpen(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'windowCovering', 'upOrOpen')

    log.info(`WindowCovering.upOrOpen called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.upOrOpen()

    // Sync state to cache - window covering opening
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.targetPositionLiftPercent100ths !== undefined) {
      stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths
    }
    if (currentState.currentPositionLiftPercent100ths !== undefined) {
      stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths
    }
    syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate)

    return result
  }

  override downOrClose(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'windowCovering', 'downOrClose')

    log.info(`WindowCovering.downOrClose called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.downOrClose()

    // Sync state to cache - window covering closing
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.targetPositionLiftPercent100ths !== undefined) {
      stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths
    }
    if (currentState.currentPositionLiftPercent100ths !== undefined) {
      stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths
    }
    syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate)

    return result
  }

  override stopMotion(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'windowCovering', 'stopMotion')

    log.info(`WindowCovering.stopMotion called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.stopMotion()

    // Sync state to cache - window covering stopped
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.targetPositionLiftPercent100ths !== undefined) {
      stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths
    }
    if (currentState.currentPositionLiftPercent100ths !== undefined) {
      stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths
    }
    syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate)

    return result
  }
}

/**
 * Custom DoorLock Server that calls plugin handlers
 */
export class HomebridgeDoorLockServer extends DoorLockServer {
  override lockDoor(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'doorLock', 'lockDoor')

    log.info(`DoorLock.lockDoor called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.lockDoor()

    // Sync lock state to cache
    const currentState = this.state as any
    if (currentState.lockState !== undefined) {
      syncEndpointStateToCache(endpointId, 'doorLock', { lockState: currentState.lockState })
    }

    return result
  }

  override unlockDoor(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'doorLock', 'unlockDoor')

    log.info(`DoorLock.unlockDoor called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.unlockDoor()

    // Sync lock state to cache
    const currentState = this.state as any
    if (currentState.lockState !== undefined) {
      syncEndpointStateToCache(endpointId, 'doorLock', { lockState: currentState.lockState })
    }

    return result
  }
}

/**
 * Custom Thermostat Server that calls plugin handlers
 */
export class HomebridgeThermostatServer extends ThermostatServer {
  override setpointRaiseLower(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'thermostat', 'setpointRaiseLower')

    log.info(`Thermostat.setpointRaiseLower called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.setpointRaiseLower(request)

    // Sync thermostat setpoints to cache
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.occupiedCoolingSetpoint !== undefined) {
      stateUpdate.occupiedCoolingSetpoint = currentState.occupiedCoolingSetpoint
    }
    if (currentState.occupiedHeatingSetpoint !== undefined) {
      stateUpdate.occupiedHeatingSetpoint = currentState.occupiedHeatingSetpoint
    }
    syncEndpointStateToCache(endpointId, 'thermostat', stateUpdate)

    return result
  }
}

/**
 * Custom Identify Server that calls plugin handlers
 */
export class HomebridgeIdentifyServer extends IdentifyServer {
  override identify(request: any): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'identify', 'identify')

    log.info(`Identify.identify called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler(request)
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    return super.identify(request)
  }
}

/**
 * Custom ColorControl Server that calls plugin handlers
 *
 * ColorControl handles color changes for lights (hue, saturation, XY color, color temperature).
 * Plugin developers can override these *Logic methods to handle color changes in their hardware.
 */
export class HomebridgeColorControlServer extends ColorControlServer {
  /**
   * Called when color temperature is changed
   * @param targetMireds - Target color temperature in mireds (micro reciprocal degrees)
   * @param transitionTime - Transition time in seconds (0 = as fast as possible)
   */
  override moveToColorTemperatureLogic(targetMireds: number, transitionTime: number): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'moveToColorTemperatureLogic')

    log.info(`ColorControl.moveToColorTemperatureLogic called for endpoint ${endpointId} with mireds=${targetMireds}, transitionTime=${transitionTime}`)

    if (handler) {
      try {
        handler({ targetMireds, transitionTime })
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.moveToColorTemperatureLogic(targetMireds, transitionTime)

    // Sync color temperature to cache
    const currentState = this.state as any
    if (currentState.colorTemperatureMireds !== undefined) {
      syncEndpointStateToCache(endpointId, 'colorControl', {
        colorTemperatureMireds: currentState.colorTemperatureMireds,
      })
    }

    return result
  }

  /**
   * Called when hue and saturation are changed together
   * @param targetHue - Target hue value (0-254 for normal hue, 0-65535 for enhanced hue)
   * @param targetSaturation - Target saturation value (0-254)
   * @param transitionTime - Transition time in seconds (0 = as fast as possible)
   */
  override moveToHueAndSaturationLogic(targetHue: number, targetSaturation: number, transitionTime: number): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'moveToHueAndSaturationLogic')

    log.info(`ColorControl.moveToHueAndSaturationLogic called for endpoint ${endpointId} with hue=${targetHue}, saturation=${targetSaturation}, transitionTime=${transitionTime}`)

    if (handler) {
      try {
        handler({ targetHue, targetSaturation, transitionTime })
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.moveToHueAndSaturationLogic(targetHue, targetSaturation, transitionTime)

    // Sync hue and saturation to cache
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.currentHue !== undefined) {
      stateUpdate.currentHue = currentState.currentHue
    }
    if (currentState.currentSaturation !== undefined) {
      stateUpdate.currentSaturation = currentState.currentSaturation
    }
    syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate)

    return result
  }

  /**
   * Called when XY color coordinates are changed
   * @param targetX - Target X value (0-65535 representing 0.0-1.0 in CIE color space)
   * @param targetY - Target Y value (0-65535 representing 0.0-1.0 in CIE color space)
   * @param transitionTime - Transition time in seconds (0 = as fast as possible)
   */
  override moveToColorLogic(targetX: number, targetY: number, transitionTime: number): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'moveToColorLogic')

    log.info(`ColorControl.moveToColorLogic called for endpoint ${endpointId} with x=${targetX}, y=${targetY}, transitionTime=${transitionTime}`)

    if (handler) {
      try {
        handler({ targetX, targetY, transitionTime })
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.moveToColorLogic(targetX, targetY, transitionTime)

    // Sync XY color to cache
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (currentState.currentX !== undefined) {
      stateUpdate.currentX = currentState.currentX
    }
    if (currentState.currentY !== undefined) {
      stateUpdate.currentY = currentState.currentY
    }
    syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate)

    return result
  }

  /**
   * Called when hue is changed individually
   * @param targetHue - Target hue value
   * @param direction - Direction to move (shortest, longest, up, down)
   * @param transitionTime - Transition time in seconds
   * @param isEnhancedHue - Whether this is enhanced hue (16-bit) or normal hue (8-bit)
   */
  override moveToHueLogic(targetHue: number, direction: any, transitionTime: number, isEnhancedHue = false): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'moveToHueLogic')

    log.info(`ColorControl.moveToHueLogic called for endpoint ${endpointId} with hue=${targetHue}, direction=${direction}, transitionTime=${transitionTime}, enhanced=${isEnhancedHue}`)

    if (handler) {
      try {
        handler({ targetHue, direction, transitionTime, isEnhancedHue })
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue)

    // Sync hue to cache
    const currentState = this.state as any
    const stateUpdate: Record<string, any> = {}
    if (isEnhancedHue && currentState.enhancedCurrentHue !== undefined) {
      stateUpdate.enhancedCurrentHue = currentState.enhancedCurrentHue
    } else if (currentState.currentHue !== undefined) {
      stateUpdate.currentHue = currentState.currentHue
    }
    syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate)

    return result
  }

  /**
   * Called when saturation is changed individually
   * @param targetSaturation - Target saturation value (0-254)
   * @param transitionTime - Transition time in seconds
   */
  override moveToSaturationLogic(targetSaturation: number, transitionTime: number): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'moveToSaturationLogic')

    log.info(`ColorControl.moveToSaturationLogic called for endpoint ${endpointId} with saturation=${targetSaturation}, transitionTime=${transitionTime}`)

    if (handler) {
      try {
        handler({ targetSaturation, transitionTime })
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    const result = super.moveToSaturationLogic(targetSaturation, transitionTime)

    // Sync saturation to cache
    const currentState = this.state as any
    if (currentState.currentSaturation !== undefined) {
      syncEndpointStateToCache(endpointId, 'colorControl', {
        currentSaturation: currentState.currentSaturation,
      })
    }

    return result
  }

  /**
   * Called when all color movement should be stopped
   */
  override stopAllColorMovement(): any {
    const endpointId = this.endpoint.id
    const handler = getHandler(endpointId, 'colorControl', 'stopAllColorMovement')

    log.info(`ColorControl.stopAllColorMovement called for endpoint ${endpointId}`)

    if (handler) {
      try {
        handler()
        log.info('  ✓ Plugin handler executed successfully')
      } catch (error) {
        log.error('  ✗ Error in plugin handler:', error)
      }
    }

    return super.stopAllColorMovement()
  }
}
